<?php
/**
 * crcl/base
 *
 * Copyright 2013 CRCL - André Sabosch - info@crcl.de
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * dealing with uris - this is the default class, could handle every uri
 * other containers, like the ini class, will add more specific methods
 *
 * generic syntax: URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
 *
 * sample:
 * foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose
 *
 * getScheme       = foo
 * getUserinfo     = username:password
 * getHost         = example.com
 * getPort         = 8042
 * getQuery        = type=animal&name=narwhal
 * getFragment     = nose
 * getAuthority    = username:password@example.com:8042
 * getPath         = /over/there/index.dtb
 * getParams       = ['type' => 'animal', 'name' => 'narwhal']
 * getHierarchical = username:password@example.com:8042/over/there/index.dtb
 *
 * urn:example:animal:ferret:nose
 *
 * getScheme       = urn
 * getUserinfo     =
 * getHost         = localhost
 * getPort         =
 * getQuery        =
 * getFragment     =
 * getAuthority    =
 * getPath         = example:animal:ferret:nose
 * getParams       =
 * getHierarchical = example:animal:ferret:nose
 *
 * @see http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
 */

namespace crcl\base\resource\reader;

interface IUri
{
  /**
   * Returns scheme of URI, always lowercase
   * URI starts with scheme to define type of uri
   * terminated by a colon (:)
   *
   * @return  string
   */
  public function getScheme();

  /**
   * Returns path of URI
   * if authority is set, path must begin with '/'
   * if no authority is set, path cannot start with '//'
   * @return  string
   */
  public function getPath();

  /**
   * Returns host of URI
   *
   * @return string
   */
  public function getHost();
  /*
  public function getUserinfo();

  public function getPort();
  public function getQuery();
  public function getFragment();
  public function getAuthority();

  public function getParams();
  public function getHierarchical();
  */
}

class ContainerUri implements IUri
{
  public function __construct($sUri)
  {
    $sUri = \trim($sUri);
    $this->sUri = $sUri;

    $this->sScheme     = $this->resolveScheme($sUri);
    $this->sAuthority = $this->resolveAuthority($sUri);
    $this->sPath      = $this->resolvePath($sUri);
    //$this->_sQuery     = $this->_resolveQuery($sUri);
    //$this->_sFragment  = $this->_resolveFragment($sUri);
  }

  public function getScheme()
  {
    return $this->sScheme;
  }

  public function getPath()
  {
    if (\is_null($this->sPath))
    {
      throw new Exception(Exception::ERROR_URI_NOPATH, array('uri' => $this->sUri));
    }
    $this->sPath = $this->decodePath($this->sPath);
    return $this->sPath;
  }

  public function getHost()
  {
    return $this->sAuthority;
  }

  /** PROTECTED ***********************************************************************************/

  protected $sAuthority = \NULL;
  protected $sUri;

  /** PRIVATE *************************************************************************************/

  private $sScheme;
  private $sPath = \NULL;

  private function resolveScheme($sUri)
  {
    //every URI should contain a scheme. If not, this class could not be loaded (see factory)
    $iPos = \strpos($sUri, ':');
    $sScheme = \substr($sUri, 0, $iPos);
    $sScheme = \strtolower($sScheme);
    return $sScheme;
  }

  private function resolveAuthority($sUri)
  {
    $sAuthority = \crcl\base\String::getInstance($sUri)->replaceFirst($this->sScheme, '');
    $iPos = \strpos($sAuthority, '//');
    if ($iPos === \FALSE)
    {
      return \NULL; //no authority
    }
    $sAuthority = \substr($sAuthority, $iPos+2);
    $iLength = \strlen($sAuthority);
    $iPos1 = \strpos($sAuthority, '/');

    //Achtung: Slash vor @ gehört zum User:Passwort
    $iPosAt = \strpos($sAuthority, '@');
    if ($iPosAt !== \FALSE)
    {
      if ($iPos1 < $iPosAt)
      {
        $iPos1 = \strpos($sAuthority, '/', $iPosAt);
      }
    }

    if ($iPos1 === \FALSE)
    {
      $iPos1 = $iLength;
    }
// cant find sample... why? //TODO - old code. not needed else?
//    else
//    {
//      if (\strpos($sAuthority, '@') !== \FALSE)
//      {
//        if ($iPos1 < \strpos($sAuthority, '@'))
//        {
//          $iPos1 = $iLength;
//        }
//      }
//    }
    $iPos2 = \strpos($sAuthority, '?');
    if ($iPos2 === \FALSE)
    {
      $iPos2 = $iLength;
    }
    $iPos3 = \strpos($sAuthority, '#');
    if ($iPos3 === \FALSE)
    {
      $iPos3 = $iLength;
    }
    $iPos = \min($iPos1, $iPos2, $iPos3);
    $sAuthority = \substr($sAuthority, 0, $iPos);
    $sAuthority = \trim($sAuthority);
    if ($sAuthority == '')
    {
      $sAuthority = \NULL;
    }
    return $sAuthority;
  }

  private function resolvePath($sUri)
  {
    $sPath = \crcl\base\String::getInstance($sUri)->replaceFirst($this->sScheme, '');

    if (!\is_null($this->sAuthority))
    {
      $sPath->replaceFirst($this->sAuthority, '');
    }
    $sPath->doNotStartWith(':');
    if (\strpos($sPath, '//') === 0)
    {
      $sPath->doNotStartWith('//');
    }
    else
    {
      $sPath->doNotStartWith('/');
    }
    $sPath->doNotStartWith('@');
    $iLength = \strlen($sPath);
    $iPos1 = \strpos($sPath, '?');
    if ($iPos1 === \FALSE)
    {
      $iPos1 = $iLength;
    }
    $iPos2 = \strpos($sPath, '#');
    if ($iPos2 === \FALSE)
    {
      $iPos2 = $iLength;
    }
    $iPos = \min($iPos1, $iPos2);
    $sPath = \substr($sPath, 0, $iPos);
    $sPath = \trim($sPath);
    if ($sPath == '')
    {
      $sPath = \NULL;
    }
    return $sPath;
  }

  private function decodePath($sPath)
  {
    $sPath = \rawurldecode($sPath);

    $sPath = \crcl\base\base\Path::replaceConstants($sPath);
    $sPath = \crcl\base\application\Path::replaceConstants($sPath);
    $sPath = \crcl\base\Tier::replaceConstants($sPath, \TRUE);

    if (\strlen($sPath) > 2)
    {
     // if (strpos($sPath, 'X:') !== FALSE)      var_dump($sPath);
      if ($sPath[1] == ':') //Drive letter
      {
        $sPath = \crcl\base\String::getInstance($sPath)
          ->doNotStartWith('/')
          ->toString();
      }
    }
    return $sPath;
  }
}

  /*
   * get authority, contains optional user information (before "@") and optional port (after ":")
   * begins with "//", ends with "/", "?", "#" or end of string
   * @return authority
   */
  //public function getAuthority();


  /*
   * get query (beginning with ?, ending on # or end of string)
   * @return  string
   */
  //public function getQuery();

  /*
   * sets query
   * forces beginning with "?"
   * @param string  $sQuery
   */
  //public function setQuery($sQuery);

  /*
   * gets fragment (beginning with #, ending with end of string)
   * @return  string
   */
  //public function getFragment();

  /*
   * sets fragment
   * forces beginning with "?"
   * @param string  $sFragment
   */
  //public function setFragment($sFragment);

    /*
   * Returns complete URI as string
   * @return  string
   */
  //public function toString();

  /*
   * Returns URI as array, keys are sScheme, sAuthority, sQuery, sPart, sFragment
   * @return  array
   */
  //public function toArray();

  /*
   * Reads multiple parts of URI from array. Parts are (array keys) sScheme, sAuthority, sQuery, sPart, sFragment
   * If a part is not set, the object property remains
   *
   * @chainable
   *
   * @param array
   */
  //public function fromArray($aUri);

  /*
   * magic __toString
   * @returns string  original URI
   */
  //public function __toString();
//}
/*
class Uri implements IUri
{
  const FIELD_SCHEME    = 'sScheme';
  const FIELD_AUTHORITY = 'sAuthority';
  const FIELD_PATH      = 'sPath';
  const FIELD_QUERY     = 'sQuery';
  const FIELD_FRAGMENT  = 'sFragment';

  //cut from path
  const FIELD_FILE          = 'sFile';
  const FIELD_FILENAME      = 'sFileName';
  const FIELD_FILEEXTENSION = 'sFileExtension';





  public function  __toString()
  {
    return $this->_sUri;
  }

  public function getScheme()
  {
    return $this->_sScheme;
  }



  public function getAuthority()
  {
    if (\is_null($this->_sAuthority))
    {
      throw new Exception(Exception::ERROR_NOAUTHORITY, array('uri' => $this->_sUri));
    }
    return $this->_sAuthority;
  }

  public function setAuthority($sAuthority)
  {
    $this->_sAuthority = $sAuthority;
  }


  public function setPath($sPath)
  {
    if (is_null($this->_sAuthority))
    {
      $sTest = \substr($sPath, 0, 2);
      if ($sTest == '//')
      {
        throw new Exception(Exception::ERROR_DOUBLESLASHNOTALLOWED, array('path' => $sPath));
      }
    }
    $sPath = \base\String::doStartWith('/', $sPath);
    $sPath = $this->_encodePath($sPath);
    $this->_sPath = $sPath;
  }

  public function getQuery()
  {
    if (\is_null($this->_sQuery))
    {
      throw new Exception(Exception::ERROR_NOQUERY, array('uri' => $this->_sUri));
    }
    return $this->_sQuery;
  }

  public function setQuery($sQuery)
  {
    $this->_sQuery = $sQuery;
  }

  public function getFragment()
  {
    if (\is_null($this->_sFragment))
    {
      throw new Exception(Exception::ERROR_NOFRAGMENT, array('uri' => $this->_sUri));
    }
    return $this->_sFragment;
  }

  public function setFragment($sFragment)
  {
    $this->_sFragment = $sFragment;
  }


  public function toString()
  {
    $sUri = '';
    if (!\is_null($this->_sScheme))
    {
      $sUri = $this->_sScheme;
      $sUri = \base\String::doEndWith(':', $sUri);
    }
    $sUri .= '//';
    if (!\is_null($this->_sAuthority))
    {
      $sUri .= $this->_sAuthority;
    }
    if (!\is_null($this->_sPath))
    {
      $sUri .= \base\String::doStartWith('/', $this->_sPath);
    }
    if (!\is_null($this->_sQuery))
    {
      $sUri .= \base\String::doStartWith('?', $this->_sQuery);
    }
    if (!\is_null($this->_sFragment))
    {
      $sUri .= \base\String::doStartWith('#', $this->_sFragment);
    }
    return $sUri;
  }

  public function toArray()
  {
    $aReturn = array();
    $aReturn[self::FIELD_SCHEME] = $this->_sScheme;
    $aReturn[self::FIELD_AUTHORITY] = $this->_sAuthority;
    $aReturn[self::FIELD_PATH] = $this->_decodePath($this->_sPath);
    $aReturn[self::FIELD_QUERY] = $this->_sQuery;
    $aReturn[self::FIELD_FRAGMENT] = $this->_sFragment;

    return $aReturn;
  }

  public function fromArray($aUri)
  {
    if (isset($aUri[self::FIELD_SCHEME]))
    {
      $this->setScheme($aUri[self::FIELD_SCHEME]);
    }
    if (isset($aUri[self::FIELD_AUTHORITY]))
    {
      $this->setAuthority($aUri[self::FIELD_AUTHORITY]);
    }
    if  (isset($aUri[self::FIELD_PATH]))
    {
      $this->setPath($aUri[self::FIELD_PATH]);
    }
    if  (isset($aUri[self::FIELD_QUERY]))
    {
      $this->setQuery($aUri[self::FIELD_QUERY]);
    }
    if  (isset($aUri[self::FIELD_FRAGMENT]))
    {
      $this->setFragment($aUri[self::FIELD_FRAGMENT]);
    }
    return $this;
  }

  public static function createFromArray($aUri)
  {
    $oUri = new Uri();
    $oUri->fromArray($aUri);
    return $oUri;
  }

  /** PROTECTED ***********************************************************************************/
/*
  protected $_sUri;


  protected $_sQuery     = \NULL;
  protected $_sFragment  = \NULL;

  protected $_sFile          = \NULL;
  protected $_sFileName      = \NULL;
  protected $_sFileExtension = \NULL;
*/
  /** PRIVATE *********************************************************************************************************/
/*



  private function _resolveQuery($sUri)
  {
    $sQuery = \base\String::replaceFirst($this->_sScheme, '', $sUri);
    $sQuery = \base\String::doNotStartWith(':', $sQuery);
    $sQuery = \base\String::doNotStartWith('//', $sQuery);
    if (!\is_null($this->_sAuthority))
    {
      $sQuery = \base\String::replaceFirst($this->_sAuthority, '', $sQuery);
    }
    $sQuery = \base\String::doNotStartWith('@', $sQuery);
    if (!\is_null($this->_sPath))
    {
      $sQuery = \base\String::replaceFirst($this->_sPath, '', $sQuery);
    }
    $sQuery = \base\String::doNotStartWith('/', $sQuery);
    $iLength = \strlen($sQuery);
    $iPos = \strpos($sQuery, '#');
    if ($iPos === \FALSE)
    {
      $iPos = $iLength;
    }
    $sQuery = \substr($sQuery, 0, $iPos);
    $sQuery = \trim($sQuery);
    $sQuery = \base\String::doNotStartWith('?', $sQuery);
    if ($sQuery == '')
    {
      $sQuery = \NULL;
    }
    return $sQuery;
  }

  private function _resolveFragment($sUri)
  {
    $sFragment = \base\String::replaceFirst($this->_sScheme, '', $sUri);
    $sFragment = \base\String::doNotStartWith(':', $sFragment);
    $sFragment = \base\String::doNotStartWith('//', $sFragment);
    if (!\is_null($this->_sAuthority))
    {
      $sFragment = \base\String::replaceFirst($this->_sAuthority, '', $sFragment);
    }
    $sFragment = \base\String::doNotStartWith('@', $sFragment);
    if (!\is_null($this->_sPath))
    {
      $sFragment = \base\String::replaceFirst($this->_sPath, '', $sFragment);
    }
    $sFragment = \base\String::doNotStartWith('/', $sFragment);
    if (!\is_null($this->_sQuery))
    {
      $sFragment = \base\String::replaceFirst($this->_sQuery, '', $sFragment);
    }
    $sFragment = \base\String::doNotStartWith('?', $sFragment);
    $sFragment = \trim($sFragment);
    $sFragment = \base\String::doNotStartWith('#', $sFragment);
    if ($sFragment == '')
    {
      $sFragment = \NULL;
    }
    return $sFragment;
  }

  private function _encodePath($sPath)
  {
    if (\strpos($sPath, '%20') === \FALSE) //not already encoded
    {
      $sPath = \rawurlencode($sPath);
    }

    $sPath = \str_replace('%2F', '/', $sPath);
    $sPath = \str_replace('%3A', ':', $sPath);
    return $sPath;
  }


}
*/


// WRITER

  /*
   * Creates URI from array (@see fromArray)
   * @param array
   * @return  IUri
   */
  //public static function createFromArray($aUri);

/*
   * set scheme. if scheme is uppercase, converts to lowercase
   * @param string
   */
  //public function setScheme($sScheme);

  /*
   * sets authoriy
   * @param string
   */
  //public function setAuthority($sAuthority);



  /*
   * sets path
   * respects authority:
   * if authority is set, path must begin with '/'
   * if no authority is set, path cannot start with '//'
   * @param string  $sPath
   */
  //public function setPath($sPath);


/*
public function setScheme($sScheme)
  {
    $sScheme = \strtolower($sScheme);
    $this->_sScheme = $sScheme;
  }
 *
 *
 */